package parser;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Stack;

import treePackage.Node;
import treePackage.Tree;

public class FloorPlan implements Cloneable {

	private Map<String, Block> blocks;
	private Map<String, Net> nets;
	private Map<String, InterfacePin> interfacePins;
	public String expression = null;
	/* Capacitance coefficients */
	private double c_vertical;
	private double c_horrizontal;

	public FloorPlan(Map<String, Block> blocks, Map<String, Net> nets,
			Map<String, InterfacePin> interfacePins, double c_vertical,
			double c_horrizontal) {

		this.blocks = blocks;
		this.nets = nets;
		this.interfacePins = interfacePins;
		this.c_vertical = c_vertical;
		this.c_horrizontal = c_horrizontal;
		Tree floorPlanTree = new Tree(blocks);
		expression = floorPlanTree.getPolishExpression().toString();
	}

	public Map<String, Block> getBlocks() {
		return blocks;
	}

	public void setBlocks(Map<String, Block> blocks) {
		this.blocks = blocks;
	}

	public Map<String, Net> getNets() {
		return nets;
	}

	public void setNets(Map<String, Net> nets) {
		this.nets = nets;
	}

	public Map<String, InterfacePin> getInterfacePins() {
		return interfacePins;
	}

	public void setInterfacePins(Map<String, InterfacePin> interfacePins) {
		this.interfacePins = interfacePins;
	}

	public double getC_vertical() {
		return c_vertical;
	}

	public void setC_vertical(double c_vertical) {
		this.c_vertical = c_vertical;
	}

	public double getC_horrizontal() {
		return c_horrizontal;
	}

	public void setC_horrizontal(double c_horrizontal) {
		this.c_horrizontal = c_horrizontal;
	}

	public FloorPlan clone() {
		Map<String, Block> newBlocksList = new HashMap<String, Block>();
		for (Block block : blocks.values()) {
			Block newBlock = block.clone();
			newBlocksList.put(newBlock.getBlockName(), newBlock);
		}
		FloorPlan newFP = new FloorPlan(newBlocksList, nets, interfacePins,
				c_vertical, c_horrizontal);
		return newFP;
	}

	private Block findBlockOfPin(Pin pin) {

		for (Block block : blocks.values()) {
			Map<String, IOPin> pinsList = block.getPinsList();
			if (pinsList.containsKey(pin.getPinName()) == true)
				return block;
		}
		return null;
	}

	public double getValueByNetAssociatedBlocks(Net net) {

		double lVert = 0.0, xCoordinatesSum = 0.0, xCoordinatesAverage = 0.0;
		double lHor = 0.0, yCoordinatesSum = 0.0, yCoordinatesAverage = 0.0;
		int counter = 0;

		Map<String, IOPin> ioPinsList = net.getIo_net_pins();
		Map<String, InterfacePin> interfacePinsList = net
				.getInterface_net_pins();

		for (IOPin pin : ioPinsList.values()) {
			Block block = findBlockOfPin(pin);
			xCoordinatesSum += pin.getX_coordinate() + block.getX_coordinate();
			yCoordinatesSum += pin.getY_coordinate() + block.getY_coordinate();
			counter++;
		}
		for (InterfacePin pin : interfacePinsList.values()) {
			xCoordinatesSum += pin.getX_coordinate();
			yCoordinatesSum += pin.getY_coordinate();
			counter++;
		}
		xCoordinatesAverage = xCoordinatesSum / counter;
		yCoordinatesAverage = yCoordinatesSum / counter;

		for (IOPin pin : ioPinsList.values()) {
			Block block = findBlockOfPin(pin);
			lHor += Math.abs(xCoordinatesAverage
					- (pin.getX_coordinate() + block.getX_coordinate()));
			lVert += Math.abs(yCoordinatesAverage
					- (pin.getY_coordinate() + block.getY_coordinate()));
		}
		for (InterfacePin pin : interfacePinsList.values()) {
			lHor += Math.abs(xCoordinatesAverage - pin.getX_coordinate());
			lVert += Math.abs(yCoordinatesAverage - pin.getY_coordinate());
		}

		return ((lVert * c_vertical) + (lHor * c_horrizontal));
	}

	public double calculatePowerFloorPlanCost() {

		double cost = 0.0;
		double costByBlocks = 0.0;
		double costByNets = 0.0;

		for (Block b : blocks.values()) {
			double aBlock = b.getCo_aBlock();
			double bBlock = b.getCo_bBlock();
			double aRblock = (b.getHorizontalLength() / b.getVerticalLength());
			double cBlock = b.getCo_cBlock();
			costByBlocks += (aBlock + bBlock * aRblock + cBlock * (1 / aRblock));
		}

		for (Net net : nets.values()) {
			double Afactor = net.getActivity_factor();
			double val = getValueByNetAssociatedBlocks(net);

			costByNets += Afactor * val;
		}

		cost = costByBlocks + costByNets;

		return cost;
	}

	private boolean netIsLegal() {

		for (Net net : nets.values()) {
			double BB_vert = net.getVertical_bb();
			double BB_hor = net.getHor_bb();

			for (InterfacePin pin1 : (net.getInterface_net_pins()).values()) {
				for (IOPin pin2 : (net.getIo_net_pins()).values()) {
					Block block = findBlockOfPin(pin2);
					if (Math.abs(pin1.getX_coordinate()
							- (pin2.getX_coordinate() + block.getX_coordinate())) > BB_hor
							|| Math.abs(pin1.getY_coordinate()
									- (pin2.getY_coordinate() + block
											.getY_coordinate())) > BB_vert)
						return false;
				}
			}

			for (IOPin pin1 : (net.getIo_net_pins()).values()) {
				for (IOPin pin2 : (net.getIo_net_pins()).values()) {
					Block block1 = findBlockOfPin(pin1);
					Block block2 = findBlockOfPin(pin2);
					if (Math.abs((pin1.getX_coordinate() + block1
							.getX_coordinate())
							- (pin2.getX_coordinate() + block2
									.getX_coordinate())) > BB_hor
							|| Math.abs((pin1.getY_coordinate() + block1
									.getY_coordinate())
									- (pin2.getY_coordinate() + block2
											.getY_coordinate())) > BB_vert)
						return false;
				}
			}
		}

		return true;
	}

	public FloorPlan doRandomChange() {

		Tree floorPlanTree = new Tree(blocks);
		expression = floorPlanTree.getPolishExpression().toString();

		String[] operands = expression.split("[HV]+");
		String[] operators = expression.split("[1-9]+");

		Random generator = new Random();
		int chosendIndex = generator.nextInt(3);

		switch (chosendIndex) {
		case 0:
			doM1Change(operands);
			break;
		case 1:
			doM2Change(operators);
			break;
		case 2:
			doM3Change(operands, operators);
			break;
		}

		String mergedExpression = mergeExp(operands, operators);

		Map<String, Block> tmpBlocksList = new HashMap<String, Block>();
		for (Block block : blocks.values()) {
			Block newBlock = block.clone();
			tmpBlocksList.put(newBlock.getBlockName(), newBlock);
		}

		doChangesInFloorPlan(mergedExpression);

		if (netIsLegal()) {
			return this;
		}

		blocks = tmpBlocksList;
		return this;
	}

	private void doM1Change(String[] operands) {

		int i = 0;
		StringBuffer operandsBuff = new StringBuffer();
		int[] arr = new int[operands.length];

		for (String str : operands) {
			operandsBuff.append(str);
			arr[i++] = str.length();
		}

		Random generator = new Random();
		int index = generator.nextInt(operandsBuff.length());

		if (index == 0) {
			swapOperands(operandsBuff, 0, 1);
		}

		else {
			int last = (operandsBuff.length() - 1);
			if (index == last) {
				swapOperands(operandsBuff, last, (last - 1));
			}

			else {
				int side = generator.nextInt(2);
				if (side == 0)
					swapOperands(operandsBuff, index, (index - 1));
				else
					swapOperands(operandsBuff, index, (index + 1));
			}

		}

		int ptr = 0;
		for (int j = 0; j < i; j++) {
			StringBuffer newOperandsBuff = new StringBuffer();
			for (int k = 0; k < arr[j]; k++) {
				newOperandsBuff.append(operandsBuff.charAt(ptr++));
			}
			operands[j] = newOperandsBuff.toString();
		}
	}

	private void swapOperands(StringBuffer operandsBuff, int index1, int index2) {
		char temp = operandsBuff.charAt(index1);
		operandsBuff.setCharAt(index1, operandsBuff.charAt(index2));
		operandsBuff.setCharAt((index2), temp);

	}

	private void doM2Change(String[] operators) {
		Random generator = new Random();

		int index = generator.nextInt(operators.length);

		if (index == 0) {
			index++;
		}

		String complemnt = chainComplement(operators[index]);

		operators[index] = complemnt;
	}

	private String chainComplement(String chain) {

		StringBuffer exp = new StringBuffer();

		for (int idx = 0; idx < chain.length(); idx++) {
			if (chain.charAt(idx) == 'V')
				exp.append("H");
			else
				exp.append("V");
		}

		return exp.toString();
	}

	private void doM3Change(String[] operands, String[] operators) {

		boolean done = false;

		while (done == false) {

			char operandToChange;
			Random generator = new Random();
			int index = generator.nextInt(operands.length);
			// 0 == left, 1 == right. Indicates the side of the
			// operator we want to swap with the chosen operand
			int side = generator.nextInt(2);

			if (index == 0 && side == 0)
				continue;

			// choosing the operator & operand we want to swap
			operandToChange = findOperandToChange(index, side, operands,
					operators);
			// checking if swapping the operator & operand keep the expression
			// legal
			if (checkIfLegalToChange(index, side, operands, operators,
					operandToChange) == 0)
				continue;
			// changing the expression
			swap(index, side, operands, operators, operandToChange);

			done = true;
		}
	}

	private char findOperandToChange(int index, int side, String[] operands,
			String[] operators) {

		// choosing the operator & operand we want to swap
		if (index == 0) {
			side = 1;
			if (operands[index].length() == 1)
				return operands[index].charAt(0);
			else
				return operands[index].charAt(operands[index].length() - 1);
		} else {
			if (index == operands.length - 1
					&& operands.length >= operators.length) {
				side = 0;
				return operands[index].charAt(0);
			} else {
				if (side == 0) {
					return operands[index].charAt(0);
				} else {
					return operands[index].charAt(operands[index].length() - 1);
				}
			}
		}
		// end of choosing the operator & operand we want to swap
	}

	private int checkIfLegalToChange(int index, int side, String[] operands,
			String[] operators, char operandToChange) {

		// checking if swapping the operator & operand keep the expression legal
		if (side == 1) {
			int operandsNum = 0, operatorsNum = 0, i;

			for (i = 0; i < index; i++) {
				operandsNum += operands[i].length();
			}
			operandsNum += operands[i].indexOf(operandToChange);
			for (i = 1; i <= index; i++) {
				operatorsNum += operators[i].length();
			}
			operatorsNum++;

			if (operandsNum <= operatorsNum)
				return 0;
		}
		return 1;
		// end of checking if swapping the operator & operand keep the
		// expression legal
	}

	private void swap(int index, int side, String[] operands,
			String[] operators, char operandToChange) {

		// changing the expression
		char theChosenOperator;
		if (side == 0) {
			theChosenOperator = operators[index].charAt(operators[index]
					.length() - 1);
			StringBuffer exp = new StringBuffer();
			for (int idx = 0; idx < operators[index].length() - 1; idx++) {
				if (operators[index].charAt(idx) == 'V')
					exp.append("V");
				else
					exp.append("H");
			}
			exp.append(operandToChange);
			operators[index] = exp.toString();
			// ---------
			StringBuffer exp2 = new StringBuffer();
			exp2.append(theChosenOperator);
			for (int idx = 1; idx < operands[index].length(); idx++) {
				exp2.append(operands[index].charAt(idx));
			}
			operands[index] = exp2.toString();
		} else {
			theChosenOperator = operators[index + 1].charAt(0);
			StringBuffer exp = new StringBuffer();
			exp.append(operandToChange);
			for (int idx = 1; idx < operators[index + 1].length(); idx++) {
				if (operators[index + 1].charAt(idx) == 'V')
					exp.append("V");
				else
					exp.append("H");
			}
			operators[index + 1] = exp.toString();
			// ---------
			StringBuffer exp2 = new StringBuffer();
			for (int idx = 0; idx < operands[index].length() - 1; idx++) {
				exp2.append(operands[index].charAt(idx));
			}
			exp2.append(theChosenOperator);
			operands[index] = exp2.toString();
		}
		// end of changing the expression
	}

	private void doChangesInFloorPlan(String mergedExpression) {

		Stack<Node> stack = new Stack<Node>();

		for (int idx = 0; idx < mergedExpression.length(); idx++) {
			if (mergedExpression.charAt(idx) != 'V'
					&& mergedExpression.charAt(idx) != 'H') {
				Block block = blocks.get("B" + mergedExpression.charAt(idx));
				Node operand = new Node(block.getBlockName(), null, null, block);
				stack.push(operand);
			} else {
				String op = mergedExpression.charAt(idx) == 'V' ? "V" : "H";
				Node firstOperand = stack.pop();
				Node secondOperand = stack.pop();
				Node operator = new Node(op, secondOperand, firstOperand);
				stack.push(operator);
			}
		}

		Node tree = stack.pop();
		blocks = tree.getBlocks();
	}

	private String mergeExp(String[] operands, String[] operators) {
		StringBuffer merge = new StringBuffer();
		int operatorsIndex = 1;
		int operatorsEnd = operators.length;

		for (String str : operands) {
			merge.append(str);
			if (operatorsIndex < operatorsEnd) {
				merge.append(operators[operatorsIndex]);
				operatorsIndex++;
			}
		}

		return merge.toString();
	}

}
